#include "aerMesh.hpp"

#define GL_BUFFER_OFFSET(i) ((char*)NULL + (i))



#if 0

// bullshit test, it seems to not working (possibly driver issue)

void VertexAttribPointer( GLuint index, GLint size, GLenum type, GLboolean normalized, 
                          GLsizei stride, const GLvoid *pointer)
{
  GLint param;
  glGetIntegerv( GL_ARRAY_BUFFER_BINDING, &param);
  
  GLuint bindingIndex = 0;
  GLuint vbo = GLuint(param);
  
  //----
  
  glBindVertexBuffer( bindingIndex, vbo, GLintptr(pointer), stride);
  glVertexAttribBinding( index, bindingIndex);
  glVertexAttribFormat( index, size, type, normalized, 0);
}
#endif









namespace aer {

Mesh::Mesh()
  : m_pIndices(NULL),
    m_packing(SINGLE_BUFFER),
    m_primitiveType(GL_POINTS),
    m_numVertices(0u),
    m_numIndices(0u)
{
}

Mesh::~Mesh()
{
  m_vertexArray.release();
  //m_vbo[kNumAttribLocation];
  m_ibo.release();
}


void Mesh::setAttrib(AttribLocation location, const float *data, 
                     U8 nComponent, U32 nElements)
{
  AER_ASSERT( data != NULL );
  AER_ASSERT( (m_numVertices == 0) || (m_numVertices == nElements)); //
  
  m_attribs[location].pData = data;
  m_attribs[location].nComponent = nComponent;
  m_numVertices = nElements;
}

void Mesh::setIndices(GLenum mode, const unsigned int *indices, U32 nElements)
{
  AER_ASSERT( indices != NULL );
  
  m_primitiveType = mode;
  m_pIndices = indices;
  m_numIndices = nElements;
}

void Mesh::upload(DeviceBuffer::Usage usage, Packing packing)
{
  // Not designed to reupload data afterward : /
  
  m_vertexArray.generate();
  m_vertexArray.bind();

  if (packing == SINGLE_BUFFER) { upload_singleBuffer(usage); }
  else 
  if (packing == INTERLEAVED)   { upload_interleaved(usage); } 
  else
  if (packing == SEPARATED)     { upload_separated(usage); }
  
  if (m_pIndices != NULL)
  {
    m_ibo.generate();
    
    m_ibo.bind( DeviceBuffer::ELEMENT_ARRAY );    
    const U32 bufferSize = m_numIndices * sizeof(unsigned int);//
    m_ibo.setData( m_pIndices, bufferSize);
    //m_ibo.unbind();
  }
  
  m_vertexArray.unbind();
  
  // reset CPU attribs buffers ?
}

void Mesh::draw() const
{
  enable();  
  
  if (m_ibo.isGenerated()) {
    glDrawElements( m_primitiveType, m_numIndices, GL_UNSIGNED_INT, 0u);
  } else {
    glDrawArrays( m_primitiveType, 0, m_numVertices );//
  }
  
  disable();
}

void Mesh::drawInstances(const U32 nInstances) const
{
  enable();
  
  if (m_ibo.isGenerated()) {
    glDrawElementsInstanced( m_primitiveType, m_numIndices, GL_UNSIGNED_INT, 0u, nInstances);
  } else {
    glDrawArraysInstanced( m_primitiveType, 0, m_numVertices, nInstances);//
  }
  
  disable();
}


void Mesh::enable() const
{
  m_vertexArray.bind();
  
  /*
  if (m_ibo.isGenerated()) { 
    m_ibo.bind();
  }
  */
  
  // only needed one time..
  for (int i=0; i<kNumAttribLocation; ++i)
  {
    if (m_attribs[i].pData != NULL) {
      glEnableVertexAttribArray( i );
    }
  }
}

void Mesh::disable() const
{
  /*
  for (int i=0; i<kNumAttribLocation; ++i) {
    glDisableVertexAttribArray( i );
  }
  
  m_ibo.unbind();
  */
  m_vertexArray.unbind();
}

void Mesh::upload_singleBuffer(DeviceBuffer::Usage usage)
{
  DeviceBuffer &vbo = m_vbo[0];
      
  vbo.generate();
  vbo.bind( DeviceBuffer::ARRAY );
  { 
    U32 bufferSize = 0u;    
    for (U8 i=0; i<kNumAttribLocation; ++i) {
      bufferSize += m_attribs[i].nComponent * m_numVertices * sizeof(float);//
    }
    vbo.setData( NULL, bufferSize, usage);
    
    
    U32 offset = 0u;
    for (U8 i=0; i<kNumAttribLocation; ++i)
    {
      Attrib &attrib = m_attribs[i];
      
      if (attrib.pData == NULL) {
        continue;
      }
      
      bufferSize = attrib.nComponent * m_numVertices * sizeof(float); //
      vbo.setSubData( attrib.pData, bufferSize, offset);

      glVertexAttribPointer(i, attrib.nComponent, GL_FLOAT, GL_FALSE, 0, 
                            GL_BUFFER_OFFSET(offset));
      offset += bufferSize;
    }
  }
  vbo.unbind();
}


void Mesh::upload_interleaved(DeviceBuffer::Usage usage)
{
  AER_ASSERT( 0 && "upload_interleaved not implemented yet\n" );
}


void Mesh::upload_separated(DeviceBuffer::Usage usage)
{
  for (U8 i=0; i<kNumAttribLocation; ++i)
  {
    Attrib &attrib = m_attribs[i];
    
    if (attrib.pData == NULL) {
      continue;
    }
    
    DeviceBuffer &vbo = m_vbo[i];
    
    vbo.generate();
    vbo.bind( DeviceBuffer::ARRAY );
      U32 bufferSize = attrib.nComponent * m_numVertices * sizeof(float);//
      vbo.setData(attrib.pData, bufferSize, usage);
      
      glVertexAttribPointer(i, attrib.nComponent, GL_FLOAT, GL_FALSE, 0, 
                            GL_BUFFER_OFFSET(0));
    vbo.unbind();
  }
}
  
} // aer

#undef GL_BUFFER_OFFSET
